Transcrição
Melhoramos a experiência do usuário, agora vamos fazer o mesmo em relação à performance da aplicação. Como estamos disparando o evento keyup, a cada preenchimento do campo de busca o valor digitado será passado para filter, e deste para o Pipe. Então, a cada caractere digitado é feita a aplicação do filtro, e isso prejudica a performance, ainda mais se a quantidade de imagens é muito grande. Pior ainda se estivéssemos realizando requisição AJAX ao servidor.
Seria melhor se, ao digitarmos "farol", fizéssemos uma pausa de 300ms, e aí sim o Pipe fosse aplicado. A vantagem disto é que evitamos a execução de uma série de operações. Para tal, usaremos um Pattern muito comum no JavaScript, para que o filtro seja atualizado somente se pararmos de digitar durante um determinado período de tempo.
A chave para o sucesso desta operação é o Subject do RxJS. Em photo-list.component.ts, criaremos inicialmente a propriedade debounce na classe PhotoListComponent, de tipo Subject, por sua vez do tipo string, que receberá um novo Subject, os quais precisarão ser importados.
Por meio de next(), é possível emitirmos um valor para Subject, que acessamos caso o tenhamos inscrito. Diferentemente do Observable, com o qual podemos inscrever e obter valores, com o Subject podemos, além disso, emitir um valor e escutá-lo, como em:
export class PhotoListComponent implements OnInit {
photos: Photo[] = [];
filter: string = '';
debounce: Subject<string> = new Subject<string>();
constructor(private activatedRoute: ActivatedRoute) { }
ngOnInit(): void {
this.photos = this.activatedRoute.snapshot.data['photos'];
this.debounce.next('f')
this.debounce.subscribe(value => alert(value));
}
}Do trecho acima, deletaremos o que se segue:
this.debounce.next('f')
this.debounce.subscribe(value => alert(value));E em photo-list.component.html, no lugar de colocarmos o valor digitado em filter, solicitaremos ao debounce para que seja feito o next(). A cada keyup será emitido um valor:
<input
class="rounded"
type="search"
placeholder="search..."
autofocus
(keyup)="debounce.next($event.target.value)"
>Voltaremos a photo-list.component.ts, em ngOnInit() nos inscreveremos em debounce, e o valor a ser emitido ali será chamado de filter, o qual receberemos, e então indicaremos que this.filter receberá filter.
ngOnInit(): void {
this.photos = this.activatedRoute.snapshot.data['photos'];
this.debounce.subscribe(filter => this.filter = filter);
}Com isso, em vez de jogarmos o valor digitado diretamente em filter, emitiremos um valor de RxJS, a ser escutado pelo subscribe(), o qual atualizará o filtro. O subscribe() será chamado enquanto o valor estiver sendo emitido. Ele é um tanto diferente do HttpClient pois este emite um único valor, e o completa, algo que não ocorre com Subject, por termos criado-o.
Tanto isto é verdade que, se voltarmos ao navegador e recarregarmos a página, o filtro funcionará bem, mesmo sem vantagens, já que estamos tendo mais trabalho, pois pegamos o valor digitado pelo usuário, passando-o para o debounce para que o Subject emita o valor por meio de next(). Tivemos que fazer o subscribe() para então levarmos o valor adiante.
Assim, em photo-list.component.ts importaremos debounceTime de rxjs/operators, junto ao qual uma série de operadores poderá ser importada. A ideia é que, antes do subscribe(), pediremos para o debounce aplicar tal operação, com a estrutura pipe(), em que incluiremos debounceTime, a receber o período de tempo. E desta operação faremos o subscribe().
ngOnInit(): void {
this.photos = this.activatedRoute.snapshot.data['photos'];
this.debounce
.pipe(debounceTime(300))
.subscribe(filter => this.filter = filter);
}A grande sacada é que, com esta alteração chamada de Lettable operators no RxJS, por usarmos o debounceTime, quando emitimos um valor no evento keyup, todas as emissões serão ignoradas, sendo consideradas após 300ms. E é isso que será repassado ao subscribe().
O Observable é engenhoso para lidar com situações deste tipo, com fluxos e eventos, e colocamos threshold, um Pipe no que chamamos de debounce, para limitar a quantidade de operações.
Resolvemos o problema de performance, porém ganhamos outro se não tomarmos cuidado: uma vez que o Observable em .subscribe(filter => this.filter = filter) nunca se completa, ele ficará guardando um espaço na memória, e se saímos deste componente e vamos a outra página, a área da memória continuará ocupada, ocasionando em memory leaking (vazamento de memória).
Então, toda vez que houver algo que fique emitindo valores infinitamente, é necessário implementar uma interface, OnDestroy, que também precisa ser implementada em photo-list.component.ts. Ao fazermos isto, o método ngOnDestroy() é acrescentado. Ele faz parte do ciclo de vida de um componente do Angular, sendo chamado toda vez que um objeto é destruído.
Significa que quando sairmos de PhotoListComponent, e ele for destruído, o método será chamado, e faremos o unsubscribe():
export class PhotoListComponent implements OnInit, OnDestroy {
/* código omitido
*/
ngOnInit(): void {
this.photos = this.activatedRoute.snapshot.data['photos'];
this.debounce
.pipe(debounceTime(300))
.subscribe(filter => this.filter = filter);
}
ngOnDestroy(): void {
this.debounce.unsubscribe();
}
}Trata-se de uma boa prática, portanto não deixemos de fazê-lo.